package cmd

import (
	"errors"
	"fmt"
	"html/template"
	"log"
	"remote_exec/util"
	"strings"

	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

func SetCommonFlags(cmd *cobra.Command) {
	cmd.PersistentFlags().IntP(util.ConstThread, "t", util.DefaultThread, "maximum number of concurrent (0 < t <= 16)")
	cmd.PersistentFlags().StringP(util.ConstConfig, "f", util.DefaultConfig, "specify servers configuration")
	cmd.PersistentFlags().StringP(util.ConstCommand, "c", util.DefaultCommand, "specify commands configuration")
	cmd.PersistentFlags().BoolP(util.ConstExecute, "e", false, "execute single command, will disable the command option")
	cmd.PersistentFlags().StringP(util.ConstNetMask, "m", "", "ip filter, e.g. 192.168.1.1 192.168.1.1,192.168.1.2 192.168.0.0/24")
	cmd.PersistentFlags().StringSliceP(util.ConstParam, "v", []string{}, "command params, e.g. key1=value1. Use parameter value via {{.key1}}")
}

func ObtainCommands(cmd *cobra.Command, args []string, cmdType string) ([]string, string, error) {
	var (
		err            error
		commandPath, _ = cmd.Flags().GetString(util.ConstCommand)
		single, _      = cmd.Flags().GetBool(util.ConstExecute)
		command        *viper.Viper
		originCommands []string
		renderCommands []string
		netMask        string
	)

	if single {
		if len(args) == 0 {
			return nil, netMask, errors.New("to execute a single command immediately, the command needs to be provided")
		}
		originCommands = append(originCommands, args[0])
		if renderCommands, err = RenderCmdWithParam(originCommands, cmd); err != nil {
			return nil, netMask, err
		}
		return renderCommands, netMask, nil
	}

	if command, err = util.LoadCfg(commandPath, util.DefaultCommand); err != nil {
		return nil, netMask, err
	}

	if len(args) > 0 {
		section := args[0]
		if !command.InConfig(section) {
			return nil, netMask, errors.New(fmt.Sprintf("no %s configuration item found", section))
		}
		originCommands = command.Sub(section).GetStringSlice(cmdType)
		netMask = command.Sub(section).GetString(util.ConstNetMask)
	} else {
		originCommands = command.GetStringSlice(cmdType)
		netMask = command.GetString(util.ConstNetMask)
	}
	if renderCommands, err = RenderCmdWithParam(originCommands, cmd); err != nil {
		return nil, netMask, err
	}
	return renderCommands, netMask, nil
}

func RenderCmdWithParam(commands []string, cmd *cobra.Command) ([]string, error) {
	var (
		params []string
		err    error
	)
	if params, err = cmd.Flags().GetStringSlice(util.ConstParam); err != nil {
		return commands, nil
	}
	var inputParam = make(map[string]string)
	for _, param := range params {
		var nameAndValue = strings.Split(param, "=")
		if len(nameAndValue) != 2 {
			continue
		}
		inputParam[strings.TrimSpace(nameAndValue[0])] = strings.TrimSpace(nameAndValue[1])
	}
	return RenderCmd(commands, inputParam)
}

func RenderCmd(commands []string, params map[string]string) ([]string, error) {
	var (
		err         error
		newCommands = make([]string, len(commands))
	)
	for idx, command := range commands {
		var tpl *template.Template
		if tpl, err = template.New(command).Option("missingkey=error").Parse(command); err != nil {
			return nil, err
		}
		var builder strings.Builder
		if err = tpl.Execute(&builder, params); err != nil {
			return nil, err
		}
		newCommands[idx] = builder.String()
	}
	return newCommands, nil
}

func ObtainHosts(cmd *cobra.Command, args []string, cmdNetMask string) ([]*util.Host, error) {
	var err error
	var configPath, _ = cmd.Flags().GetString(util.ConstConfig)
	var netMask, _ = cmd.Flags().GetString(util.ConstNetMask)
	var allHosts []*util.Host
	if allHosts, err = util.ParseHosts(configPath); err != nil {
		return nil, err
	}
	if len(netMask) == 0 && len(cmdNetMask) == 0 {
		return allHosts, nil
	}
	var matchHosts []*util.Host
	var effectiveMask = util.Nvl(len(netMask) > 0, netMask, cmdNetMask)

	log.Printf("use netmask [%s].\n", effectiveMask)
	for _, host := range allHosts {
		if util.IpAllow(host.Host, effectiveMask) {
			matchHosts = append(matchHosts, host)
		}
	}
	return matchHosts, nil
}
